Skip to content

Skip backtrace capture on internal validation exceptions#2687

Merged
ericproulx merged 1 commit intomasterfrom
perf/skip-validation-backtrace
Apr 20, 2026
Merged

Skip backtrace capture on internal validation exceptions#2687
ericproulx merged 1 commit intomasterfrom
perf/skip-validation-backtrace

Conversation

@ericproulx
Copy link
Copy Markdown
Contributor

@ericproulx ericproulx commented Apr 19, 2026

Summary

Grape::Exceptions::Validation is raised once per bad attribute on every failing request; Grape::Exceptions::ValidationArrayErrors wraps them and is raised once per validator. Both are caught internally by Endpoint#run_validators and their backtraces are never surfaced — they point into Grape's own validator stack, not user code, and have no diagnostic value.

Ruby's raise only captures a backtrace if the exception's backtrace ivar is nil. Pre-seeding it via set_backtrace(EMPTY_BACKTRACE) in initialize makes raise skip the C-level make_backtrace call entirely.

Backward compatibility

The outer Grape::Exceptions::ValidationErrors is untouched — its backtrace is preserved for anyone doing rescue_from ValidationErrors or shipping validation failures to error-reporting tools (Sentry, Rollbar, etc.). Only the two inner exceptions — which are private to the validator internals — have their backtrace capture skipped.

Perf

Measured on a synthetic benchmark at stack depth 40:

Before After
raise + rescue 1.07 µs 0.65 µs
backtrace length 1 frame 0 frames

Savings scale linearly with stack depth since make_backtrace is O(frames). At representative prod stack depth (Rack + middleware chain + Grape + validators, typically 80-150 frames) this is closer to ~1-2 µs per raise.

A request failing 5 attributes across 2-3 validators raises ~7-8 inner exceptions → ~7-15 µs per failing request. Failure paths on public APIs get hammered by bots and misconfigured clients, so at scale this adds up.

Test plan

  • Full RSpec suite passes locally (670 validation specs, 0 failures).
  • CI green.

🤖 Generated with Claude Code

@ericproulx ericproulx force-pushed the perf/skip-validation-backtrace branch from 1c18146 to 44fe03a Compare April 19, 2026 17:37
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 19, 2026

Danger Report

No issues found.

View run

@ericproulx ericproulx force-pushed the perf/skip-validation-backtrace branch from 44fe03a to e527b71 Compare April 19, 2026 17:38
Grape::Exceptions::Validation is raised once per bad attribute on every
failing request; Grape::Exceptions::ValidationArrayErrors wraps them and
is raised once per validator. Both are caught internally by
Endpoint#run_validators and their backtraces are never surfaced — they
point into Grape's own validator stack, not user code, and have no
diagnostic value.

Ruby's +raise+ only captures a backtrace if the exception's backtrace
ivar is nil. Pre-seeding it via +set_backtrace(EMPTY_BACKTRACE)+ in
+initialize+ makes +raise+ skip the C-level +make_backtrace+ call
entirely. At representative prod stack depth (80-150 frames) this saves
~1-2 µs per raise.

The outer Grape::Exceptions::ValidationErrors is untouched — its
backtrace is preserved for anyone doing +rescue_from ValidationErrors+
or shipping validation failures to error-reporting tools.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ericproulx ericproulx force-pushed the perf/skip-validation-backtrace branch from e527b71 to c176526 Compare April 20, 2026 22:33
@ericproulx ericproulx merged commit 5ad002d into master Apr 20, 2026
80 checks passed
@ericproulx ericproulx deleted the perf/skip-validation-backtrace branch April 20, 2026 22:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants